# this file defines all important behaviour of the app.
import os
import time
import copy

import task
from recipe import Recipe
from utils import log

# configuration constants
SERVER_ADDRESS  = '127.0.0.1'
SERVER_PORT  = 8081

QUERY_DELAY = 20      # how much to wait between queries? (in seconds)
PROMOTION_DELAY = 5   # how much to wait between parallel tasks promotion.
RECIPE_PATH = '../recipe_database/'

# some global variables
keyword_dictionary=None     # Defined here so functions can use it.
task_list= []               # parsed tasks
loaded_recipe = None 
is_running = False          # should idle func promote available tasks?
last_announcment_time =None # last time speech
last_queried_task =None     # actually, last relevant task
time_marker = None
time_started_recipe = None



def command(s):
    '''execute command in string s.
    first in split is command, next is passed as argument.'''
    cmd= s.split()
    if cmd[0] not in keyword_dictionary:
        print 'unkown command.'
        return
    func=keyword_dictionary[cmd[0]][1]
    if not func:
        return
    if len(cmd)==1:
        func()
    else:
        func(cmd[1])

def _calculate_last_queried_task(task_array):
    lt=None
    for t in task.find_active_tasks(task_array):
        if lt==None or t.time_started > lt.time_started:
            lt=t
    return lt

def set_last_queried_task():
    '''search which task was last announced'''
    global last_queried_task
    lt = _calculate_last_queried_task(task_list)
    last_queried_task = lt


def _gui_show_all():
    os.system('cls' if os.name=='nt' else 'clear') # clear screen

    eta_dict = _get_eta_dict(time.time(), task_list)
        
    if last_show:
        print "%s\n\n" % last_show
    print "%.2f seconds passed ..." % (time_marker - time_started_recipe)
    print "%d total tasks:  %d ready, %d active, %d done, %d overdue" % (len(task_list),
                                                                         len(task.find_available_tasks(task_list)),
                                                                         len(task.find_active_tasks(task_list)),
                                                                         len(task.find_done_tasks(task_list)),
                                                                         len(task.find_overdue_tasks(task_list)))
    print "-="*16
    tasks = sorted(task_list, key= lambda t: (eta_dict[t.index], t.index))
    for t in tasks:
        if t.completed():
            print "%d) [%-50s] - Done! (from %.1f to %.1f)" % (t.index, t.speech, 
                                                  t.time_started - time_started_recipe, 
                                                  t.time_finished - time_started_recipe)
        elif t.is_overdue():
            print "%d) > [%-50s] - OVERDUE!! < (%.1f, estimated duration=%s)" % (t.index, t.speech, 
                                                                        t.time_started - time_started_recipe, 
                                                                        t.duration)
        elif t.is_active():
            print "%d) >> [%-50s] <<  (%.1f, estimated duration=%s)" % (t.index, t.speech, 
                                                                   t.time_started - time_started_recipe, 
                                                                   t.duration)
        elif t.started():
            print "%d) ERROR!!!  started but not active or completed!  [%s]" % (t.index, t.speech)
        else:
            can_start = ", CAN START!" if t.can_start() else ""
            print "%d) [%-50s]  (estimated start in %.1f seconds, duration=%s%s)" % (t.index, t.speech, 
                                                                                     eta_dict[t.index]-t.duration  - time_started_recipe, 
                                                                                     t.duration, 
                                                                                     can_start)
             

OLD_STYLE = False

def _gui_show_time_passed():
    if OLD_STYLE:
        print "... %.2f seconds passed ..." % (time_marker - time_started_recipe)
        print "%d total tasks:  %d ready, %d active, %d done, %d overdue" % (len(task_list),
                                                                             len(task.find_available_tasks(task_list)),
                                                                             len(task.find_active_tasks(task_list)),
                                                                             len(task.find_done_tasks(task_list)),
                                                                             len(task.find_overdue_tasks(task_list)))
    else:
        _gui_show_all()

last_show = ""

def _gui_show_task_announcment(task_announce):
    if OLD_STYLE:
        print '\n'+task_announce.speech+'\n'
    else:
        global last_show
        last_show = task_announce.speech
        _gui_show_all()
        
def _gui_show_query(task_query):
    if OLD_STYLE:
        print '\n'+task_query.query+'\n'
    else:
        global last_show
        last_show = '>>> '+task_query.query+' <<<' 
        _gui_show_all()
    

def _get_eta_dict(time_marker, task_array):
    '''simulate playing the recipe and returns the eta of the upcoming tasks
    
    returns: dictionary of task_id to eta
    '''
    # create a copy of the task-array
    ta_copy = [copy.copy(t) for t in task_array]
    for t in ta_copy:
        # replace pointers to dependencies with pointers to the copies
        t.dependencies = [ta_copy[d.index] for d in t.dependencies]

    # copy global variables
    _last_announcment_time = last_announcment_time
    _last_queried_task = last_queried_task
    
    # this is based on the player logic in _run_recipe_background_thread
    # TODO:  DRY (don't repeat yourself)
    #        somehow combine this simulated recipe player logic with the real player logic
    current_time = time_marker - 0.5
    # we are gonna announce stuff. Don't do so too freq.
    while True:
        if not task.find_active_tasks(ta_copy) and not task.find_available_tasks(ta_copy):
            break
        
        current_time += 0.5
        
        # fake completion of tasks - when active past their duration
        for t in task.find_active_tasks(ta_copy):
            if t.is_overdue(current_time):
                t.stop(current_time)
        
        if _last_announcment_time!=None and (current_time-_last_announcment_time < PROMOTION_DELAY):
            continue

        # promote available tasks
        at= task.find_available_tasks(ta_copy)
        if at:
            t = at[0]
            t.start(current_time)
            _last_announcment_time = current_time
            _last_queried_task = t
            continue
    
        # no need to query for overdue tasks - we automatically stop them above
    
    eta_dict = dict((t.index, t.ETA()) for t in ta_copy)
    return eta_dict
        

def _run_recipe_background_thread():
    global time_marker
    global task_list
    if not task.find_active_tasks(task_list) and not task.find_available_tasks(task_list):
        is_running=False
        time_marker = None
        print '\nrecipe finished execution!\n'
        return

    # print elapsed time
    if not time_marker:
        time_marker = time.time()
    dt=time.time()-time_marker
    if dt>5.0:
        time_marker +=dt
        _gui_show_time_passed()

    # we are gonna announce stuff. Don't do so too freq.
    global last_announcment_time
    if last_announcment_time!=None and (time.time()-last_announcment_time < PROMOTION_DELAY):
        return

    # promote available tasks
    global last_queried_task
    at= task.find_available_tasks(task_list)
    if at:
        t = at[0]
        t.start()
        _gui_show_task_announcment(t)
        last_announcment_time = time.time()
        last_queried_task = t
        return

    # query for overdue tasks
    at= task.find_overdue_tasks(task_list)
    for t in at:
        if t.time_last_queried==None or time.time()-t.time_last_queried> QUERY_DELAY:
            t.query_now()
            if t.query==None and t!=last_queried_task:
                t.stop()
                set_last_queried_task()
                continue
            _gui_show_query(t)
            last_announcment_time = time.time()
            last_queried_task = t
            return


def idle():
    '''function running when idle'''
    # should this function run?
    global is_running
    if not is_running:
        return

    _run_recipe_background_thread()

def help_func():
    '''show help on available commands'''
    print 'commands\n'
    for k,v in keyword_dictionary .items():
        print '\t- '+k+v[0]

def run_func():
    global is_running, time_started_recipe
    if is_running:
        print '\n Already running...'
        return
    is_running=True
    if not time_started_recipe:
        time_started_recipe = time.time()
    print '\n\nStart cooking !!!\n\n'

def list_func():
    print 'listing recipes in '+RECIPE_PATH
    fns= os.listdir(RECIPE_PATH)
    i=0
    for fn in fns:
        if not fn.endswith('.txt'):
            continue
        i=i+1
        print "%d) %s" % (i, fn)
        
def json_list_func():
    print 'listing json recipes in '+RECIPE_PATH
    fns= os.listdir(RECIPE_PATH)
    i=0
    for fn in fns:
        if not fn.endswith('.json'):
            continue
        i=i+1
        print "%d) %s" % (i, fn)
    
def parse_func(sn=None):
    if sn==None:
        print '\nyou forgot to specify the number of recipe to parse\n'
        return
    fns= os.listdir(RECIPE_PATH)
    # filter out non txt files:
    ffn=[]
    for fn in fns:
        if fn.endswith('.txt'):
            ffn.append(fn)
    global task_list, loaded_recipe
    loaded_recipe = Recipe.parse_file(RECIPE_PATH+ffn[int(sn)-1])
    task_list = loaded_recipe.task_list
    print 'parsed',len(task_list),'tasks\n'
    
def json_parse_func(sn=None):
    if not sn:
        print '\nyou forgot to specify the number of recipe to parse\n'
        return
    fns= os.listdir(RECIPE_PATH)
    # filter out non txt files:
    ffn=[]
    for fn in fns:
        if fn.endswith('.json'):
            ffn.append(fn)
    global task_list, loaded_recipe
    loaded_recipe = Recipe.parse_json_file(RECIPE_PATH+ffn[int(sn)-1])
    task_list = loaded_recipe.task_list
    log( 'parsed %d tasks\n' % len(task_list))

def tasks_func():
    print 'Listing all tasks:'
    for t in task_list:
        print 'speech:',t.speech
    print ''

def available_func():
    print 'Listing all tasks ready to run:'
    for t in task_list:
        if t.can_start():
            print 'speech:',t.speech
    print ''

def active_func():
    print 'Listing all active tasks:'
    for t in task_list:
        if t.is_active():
            print 'speech:',t.speech
    print ''

def done_func():
    '''Change the status of the last queried task to complete.'''
    if last_queried_task==None or not last_queried_task.is_active():
        return
    last_queried_task.stop()
    global last_announcment_time
    last_announcment_time -= PROMOTION_DELAY
    set_last_queried_task()

def repeat_func():
    set_last_queried_task()
    if last_queried_task==None:
        print '\nCurrently there is no active task\n'
        return
    print'\n'+last_queried_task.speech+'\n'
    
def save_json_func():
    loaded_recipe.save_as_json()

def stop_func():
    global is_running
    if not is_running:
        return
    is_running=False
    print('\nrecipe execution stopped\n')

# putting some content into the dictionary.
# server words, some minimal help, and corresponding functions
keyword_dictionary ={
    'help':['        show help',                                      help_func      ],
    'exit':['        stop server and client',                         None           ],
    'run':['         start running parsed recipe',                    run_func       ],
    'list':['        list all available recipes',                     list_func      ],
    'parse':['       parse recipe number #',                          parse_func     ],
    'available':['   among parsed tasks, list thos ready to run',     available_func ],
    'tasks':['       list all parsed tasks',                          tasks_func     ],
    'done':['        tell the server current task is done',           done_func      ],
    'yes':['         tell the server current task is done',           done_func      ],
    'active':['      list active tasks',                              active_func    ],
    'repeat':['      repeat last task',                               repeat_func    ],
    'stop':['        stop recipe execution',                          stop_func      ],
    'json_save':['   save last loaded recipe as json',                save_json_func ],
    'jlist':['       list all available json recipes',                json_list_func ],
    'jparse':['      parse json recipe number #',                     json_parse_func]
}




